﻿Imports Q2Q.Audio
Imports NAudio.Wave
Imports System.Windows

Namespace Controls
	''' <summary>
	''' Interaction logic for WaveFormControl.xaml
	''' </summary>
	Partial Public Class WaveFormControl
		Inherits UserControl

		''' <summary>
		''' The audio file to find a wave form for.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property AudioFile As String
			Get
				Return GetValue(AudioFileProperty)
			End Get

			Set(ByVal value As String)
				If GetValue(AudioFileProperty) <> value Then
					SetValue(AudioFileProperty, value)
					If Not IsInitializing Then
						InitializeWaveFormThread()
					End If
				End If
			End Set
		End Property
		''' <summary>
		''' Represents the AudioFile Property.
		''' </summary>
		''' <remarks></remarks>
		Public Shared ReadOnly AudioFileProperty As DependencyProperty = _
							   DependencyProperty.Register("AudioFile", _
							   GetType(String), GetType(WaveFormControl), _
							   New PropertyMetadata(""))


		Private Delegate Sub ClearCanvasDelegate()
		Private Sub ClearCanvas()
			mainCanvas.Children.Clear()
			'wave.Data = Nothing
			RenderLocation = 0
		End Sub

		''' <summary>
		''' Used internally to determine whether the control is initializing the view of the waveform.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Private Property IsInitializing As Boolean = False

		''' <summary>
		''' Creates a new thread to draw the waveform display.
		''' </summary>
		''' <remarks></remarks>
		Private Sub InitializeWaveFormThread()
			Loader.Visibility = Visibility.Visible
			Dim t As New System.Threading.Thread(AddressOf InitializeWaveForm)
			t.SetApartmentState(System.Threading.ApartmentState.STA)
			t.Start(AudioFile)
		End Sub

		''' <summary>
		''' Reads in the audio file and creates handlers to perform actions when the predetermined numbers of samples have been processed.
		''' </summary>
		''' <param name="Audio"></param>
		''' <remarks></remarks>
		Private Sub InitializeWaveForm(Audio As String)
			If Not FileIO.FileSystem.FileExists(Audio) Then
				Dim d As New RevealErrorDelegate(AddressOf RevealError)
				Dim p(0) As String
				p(0) = "There is currently no audio file selected to show a waveform preview for, or else the selected audio file doesn't exist in the specified location."
				Me.Dispatcher.Invoke(d, p)
				Return
			ElseIf IsInitializing Then
				Return
			End If

			Try
				IsInitializing = True
				Dim d As New ClearCanvasDelegate(AddressOf ClearCanvas)
				Me.Dispatcher.Invoke(d)
				Dim r As New NAudio.Wave.AudioFileReader(Audio)
				Dim a As New SampleAggregator(r)

				a.NotificationCount = r.WaveFormat.SampleRate / My.Settings.WaveFormResolution
				AddHandler a.MaximumCalculated, AddressOf MaximumCalculated

				Dim b(r.Length) As Single
				Dim bytesRead As Integer = 0
				Dim doRead As Boolean = True
				Do While doRead
					Dim startReading As Integer = bytesRead
					bytesRead += a.Read(b, bytesRead, r.WaveFormat.SampleRate / My.Settings.WaveFormResolution)
					If bytesRead = startReading Then
						doRead = False
					End If
				Loop

				Dim d2 As New WriteTimesDelegate(AddressOf WriteTimes)
				Dim p(0) As Object
				p(0) = mainCanvas.ActualWidth / r.TotalTime.TotalSeconds * 60
				Me.Dispatcher.Invoke(d2, p)

				RemoveHandler a.MaximumCalculated, AddressOf MaximumCalculated

				IsInitializing = False

				Dim d3 As New RevealWaveformDelegate(AddressOf RevealWaveform)
				Me.Dispatcher.Invoke(d3)
			Catch ex As Exception
				Dim d As New RevealErrorDelegate(AddressOf RevealError)
				Dim p(0) As String
				p(0) = ex.Message
				Me.Dispatcher.Invoke(d, p)
				IsInitializing = False
			End Try
		End Sub

		''' <summary>
		''' A Delegate sub for use with the RevealWaveform sub.
		''' </summary>
		''' <remarks></remarks>
		Private Delegate Sub RevealWaveformDelegate()

		Private Sub RevealWaveform()
			Loader.Visibility = Windows.Visibility.Collapsed
		End Sub

		''' <summary>
		''' A Delegate sub for use with the WriteTimes sub.
		''' </summary>
		''' <param name="Minutes"></param>
		''' <remarks></remarks>
		Private Delegate Sub WriteTimesDelegate(Minutes As Object)

		''' <summary>
		''' Creates the visual framework for the time display.
		''' </summary>
		''' <param name="Minutes"></param>
		''' <remarks></remarks>
		Private Sub WriteTimes(Minutes As Object)
			Try
				For i = 0 To mainCanvas.ActualWidth Step Minutes
					Dim l As New Line()
					l.X1 = i
					l.X2 = i
					l.Y1 = 0
					l.Y2 = MyHeight
					l.StrokeThickness = 1
					l.Stroke = Media.Brushes.Red
					mainCanvas.Children.Add(l)
				Next
			Catch ex As Exception
				Throw
			End Try

		End Sub

		''' <summary>
		''' DelegateSub for use with the RevealError sub.
		''' </summary>
		''' <param name="Msg"></param>
		''' <remarks></remarks>
		Private Delegate Sub RevealErrorDelegate(Msg As String)

		''' <summary>
		''' Writes an error message to the control's canvas to explain why the waveform hasn't loaded.
		''' </summary>
		''' <param name="Msg"></param>
		''' <remarks></remarks>
		Private Sub RevealError(Msg As String)
			ErrorDetails.Text = Msg
			Errors.Visibility = Windows.Visibility.Visible
		End Sub

		''' <summary>
		''' Creates a new WaveFormControl.
		''' </summary>
		''' <remarks></remarks>
		Public Sub New()
			InitializeComponent()
		End Sub

		''' <summary>
		''' The location the next line will be drawn to as the wave form is drawn.
		''' </summary>
		''' <remarks></remarks>
		Private RenderLocation As Double = 0

		''' <summary>
		''' Fired when a the max and min amplitude values are calculated. This dispatches the DrawWaveForm sub to draw the next line.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Public Async Sub MaximumCalculated(sender As Object, e As MaxSampleEventArgs)
			Dim d As New DrawWaveFormDelegate(AddressOf DrawWaveForm)
			Dim param(1) As Object
			param(0) = sender
			param(1) = e
			Await Me.Dispatcher.BeginInvoke(d, param)
		End Sub

		''' <summary>
		''' Delegate Sub for use with the DrawWaveForm sub.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Delegate Sub DrawWaveFormDelegate(sender As Object, e As MaxSampleEventArgs)

		''' <summary>
		''' Used to draw a line to the waveform canvas.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Public Sub DrawWaveForm(sender As Object, e As MaxSampleEventArgs)
			Dim l As New Line()
			l.X1 = RenderLocation
			l.X2 = RenderLocation
			Dim m As Double = Me.MyHeight / 2
			l.Y1 = m + (e.MaxSample * (m - 20))
			l.Y2 = m + (e.MinSample * (m - 20))
			l.StrokeThickness = 1
			l.Stroke = Media.Brushes.Black
			l.Fill = Brushes.Transparent
			mainCanvas.Children.Add(l)
			mainCanvas.Width = RenderLocation
			RenderLocation += 1
		End Sub

		''' <summary>
		''' Represents the height of the control when it starts its layout.
		''' </summary>
		''' <remarks></remarks>
		Private MyHeight As Double = 0

		''' <summary>
		''' Represents the width of the control when it starts its layout.
		''' </summary>
		''' <remarks></remarks>
		Private MyWidth As Double = 0

		''' <summary>
		''' Runs when the control is loaded to set the canvas height. Then starts the drawing of the waveform.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub WaveFormControl_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
			MyHeight = Me.ActualHeight
			MyWidth = Me.ActualWidth
			mainCanvas.Height = MyHeight
			If Not IsInitializing Then
				InitializeWaveFormThread()
			End If
		End Sub



	End Class
End Namespace